package com.idanchuang.component.logback.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.idanchuang.component.logback.enums.MaskType;
import com.idanchuang.component.logback.helper.LogContext;
import com.zoi7.component.core.base.BaseClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.util.Map;

public class LogUtil extends BaseClass {

    private static final Logger log = LoggerFactory.getLogger(LogUtil.class);

    /**
     * 对 json串 进行脱敏, 并指定需要脱敏的key以及脱敏方式
     * @param jsonString 需要脱敏的json字符串
     * @param maskKey 必须指定一个脱敏的key
     * @param maskKeys 可以指定额外脱敏的key
     * @return 脱敏后的字符串
     */
    public static String maskJson(String jsonString, MaskKey maskKey, MaskKey... maskKeys) {
        if (isBlank(jsonString)) {
            return jsonString;
        }
        try {
            JSONObject jsonObject = JSON.parseObject(jsonString);
            return maskJson(jsonObject, maskKey, maskKeys);
        } catch (Exception e) {
            LogContext.INTERNAL_LOGGER.error("maskJson failed", e);
            return jsonString;
        }
    }

    /**
     * 对 json对象 进行脱敏, 并指定需要脱敏的key以及脱敏方式
     * @param jsonObject 需要脱敏的json对象
     * @param maskKey 必须指定一个脱敏的key
     * @param maskKeys 可以指定额外脱敏的key
     * @return 脱敏后的对象字符串
     */
    public static String maskJson(JSONObject jsonObject, MaskKey maskKey, MaskKey... maskKeys) {
        if (jsonObject == null) {
            return "";
        }
        try {
            return JSON.toJSONString(dealJsonMask("$", jsonObject, integrateMaskKey(maskKey, maskKeys)));
        } catch (Exception e) {
            LogContext.INTERNAL_LOGGER.error("maskJson failed", e);
            return JSON.toJSONString(jsonObject);
        }
    }

    /**
     * 对 map对象 进行脱敏, 并指定需要脱敏的key以及脱敏方式
     * @param mapData 需要脱敏的map对象
     * @param maskKey 必须指定一个脱敏的key
     * @param maskKeys 可以指定额外脱敏的key
     * @return 脱敏后的对象字符串
     */
    public static String maskMap(Map<String, Object> mapData, MaskKey maskKey, MaskKey... maskKeys) {
        if (mapData == null) {
            return "";
        }
        MaskKey[] maskKeys1 = integrateMaskKey(maskKey, maskKeys);
        for (String key : mapData.keySet()) {
            Object v = mapData.get(key);
            if (!(v instanceof String)) {
                continue;
            }
            for (MaskKey mk : maskKeys1) {
                if (mk.getKeys().contains(key)) {
                    mapData.put(key, mask((String)v, mk.getMaskType()));
                }
            }
        }
        return JSON.toJSONString(mapData);
    }

    /**
     * 对 json字符串 进行脱敏, 并指定其映射的Java类(以此类中的字段注解为脱敏目标)
     * @param jsonString 需要脱敏的json对象
     * @param classType 必须指定一个脱敏的key
     * @return 脱敏后的对象字符串
     */
    public static String maskJson(String jsonString, Class<?> classType) {
        if (isBlank(jsonString)) {
            return jsonString;
        }
        try {
            Object object = JSON.parseObject(jsonString, classType);
            return maskObject(object);
        } catch (Exception e) {
            LogContext.INTERNAL_LOGGER.error("maskJson failed", e);
            return jsonString;
        }
    }

    /**
     * 序列化并脱敏一个对象
     * @param object 对象
     * @return 脱敏后的字符串
     */
    public static String maskObject(Object object) {
        if (object == null) {
            return null;
        }
        return JSON.toJSONString(object, new MaskValueFilter());
    }

    /**
     * 脱敏所有匹配的敏感类型(手机号/身份证/银行卡/AKSK/邮箱)
     * @param message 原始字符串
     * @return 脱敏后
     */
    public static String maskAll(String message) {
        return mask(message, MaskType.PATTERN_ALL);
    }

    /**
     * 按指定的类型脱敏字符串
     * @param message 字符串
     * @param maskType 脱敏类型
     * @return 脱敏后
     */
    public static String mask(String message, MaskType maskType) {
        if (isBlank(message)) {
            return message;
        }
        switch (maskType) {
            case NAME:
                return maskName(message);
            case PASSWORD:
                return maskPassword(message);
            case ADDRESS:
                return maskAddress(message);
            case KEEP_HALF_FRONT:
                return keepFront(message, message.length() / 2);
            case KEEP_HALF_BACK:
                return keepBack(message, message.length() / 2);
            case KEEP_ENDPOINT:
                return keepEndpoint(message);
            case MOBILE:
                return maskMobile(message);
            case ID_CARD:
                return maskIdCard(message);
            case BANK_NUMBER:
                return maskBankNumber(message);
            case EMAIL:
                return maskEmail(message);
            case AK_SK:
                return maskAKSK(message);
            case PATTERN_ALL:
                message = maskMobileAuto(message);
                message = maskIdCardAuto(message);
                message = maskBankNumberAuto(message);
                message = maskEmailAuto(message);
                message = maskAKSKAuto(message);
                return message;
            default:
                return message;
        }
    }

    /**
     * 脱敏姓名 (保留前1个字符)
     * @param name 姓名
     * @return 脱敏后的姓名
     */
    public static String maskName(String name) {
        if (name == null) {
            return null;
        }
        return keepFront(name, 1);
    }

    /**
     * 脱敏密码 (替换为 **** )
     * @param password 密码
     * @return 脱敏后的密码
     */
    public static String maskPassword(String password) {
        return "****";
    }

    /**
     * 脱敏地址 (保留前4个字符)
     * @param address 地址
     * @return 脱敏后的地址
     */
    public static String maskAddress(String address) {
        if (address == null) {
            return null;
        }
        return keepFront(address, 4);
    }

    /**
     * 脱敏手机号
     * @param mobile 手机号
     * @return 脱敏后
     */
    public static String maskMobile(String mobile) {
        if (mobile == null) {
            return null;
        }
        String origin = mobile;
        // whole mobile
        mobile = mobile.replaceAll("((?:^|[^\\d])\\+\\d{2}\\s?1\\d{2})\\d{4}(\\d{4})([^\\d]|$)", "$1****$2$3");
        // mobile
        mobile = mobile.replaceAll("((?:^|[^\\d])1\\d{2})\\d{4}(\\d{4})([^\\d]|$)", "$1****$2$3");
        if (mobile.equals(origin)) {
            return keepBack(mobile, 4);
        }
        return mobile;
    }

    /**
     * 脱敏手机号
     * @param mobile 手机号
     * @return 脱敏后
     */
    public static String maskMobileAuto(String mobile) {
        if (mobile == null) {
            return null;
        }
        // whole mobile
        mobile = mobile.replaceAll("((?:^|[^\\d])\\+\\d{2}\\s?1\\d{2})\\d{4}(\\d{4})([^\\d]|$)", "$1****$2$3");
        // mobile
        mobile = mobile.replaceAll("((?:^|[^\\d])1\\d{2})\\d{4}(\\d{4})([^\\d]|$)", "$1****$2$3");
        return mobile;
    }

    /**
     * 打印一个info日志, 并且追加原始日志
     * @param maskMessage 手动脱敏后的日志
     * @param originMessage 手动脱敏前的原始日志
     */
    public static void logInfo(String maskMessage, Object originMessage) {
        String origin = originMessage != null ? originMessage.toString() : "";
        log.info(maskMessage + " " + LogbackCryptUtil.encrypt(origin));
    }

    /**
     * 打印一个warn日志, 并且追加原始日志
     * @param maskMessage 手动脱敏后的日志
     * @param originMessage 手动脱敏前的原始日志
     */
    public static void logWarn(String maskMessage, Object originMessage) {
        String origin = originMessage != null ? originMessage.toString() : "";
        log.warn(maskMessage + " " + LogbackCryptUtil.encrypt(origin));
    }

    /**
     * 打印一个error日志, 并且追加原始日志
     * @param maskMessage 手动脱敏后的日志
     * @param originMessage 手动脱敏前的原始日志
     */
    public static void logError(String maskMessage, Object originMessage) {
        String origin = originMessage != null ? originMessage.toString() : "";
        log.error(maskMessage + " " + LogbackCryptUtil.encrypt(origin));
    }

    /**
     * 脱敏身份证号
     * @param idCard 带身份证号的字符串
     * @return 脱敏后
     */
    public static String maskIdCard(String idCard) {
        if (idCard == null) {
            return null;
        }
        String origin = idCard;
        // id card
        idCard = idCard.replaceAll("((?:^|[^\\d])[1-9]\\d{5})(?:18|19|20|21)\\d{2}(?:(?:0[1-9])|(?:1[0-2]))(?:(?:[0-2][1-9])|10|20|30|31)\\d{2}(\\d[0-9Xx])([^\\d]|$)", "$1**********$2$3");
        if (idCard.equals(origin)) {
            return keepEndpoint(idCard, 2, 2);
        }
        return idCard;
    }

    /**
     * 脱敏身份证号
     * @param idCard 带身份证号的字符串
     * @return 脱敏后
     */
    public static String maskIdCardAuto(String idCard) {
        if (idCard == null) {
            return null;
        }
        // id card
        idCard = idCard.replaceAll("((?:^|[^\\d])[1-9]\\d{5})(?:18|19|20|21)\\d{2}(?:(?:0[1-9])|(?:1[0-2]))(?:(?:[0-2][1-9])|10|20|30|31)\\d{2}(\\d[0-9Xx])([^\\d]|$)", "$1**********$2$3");
        return idCard;
    }

    /**
     * 脱敏银行卡
     * @param bankNumber 带银行卡的字符串
     * @return 脱敏后
     */
    public static String maskBankNumber(String bankNumber) {
        if (bankNumber == null) {
            return null;
        }
        return keepEndpoint(bankNumber, 2, 2);
    }

    /**
     * 脱敏银行卡 (自动脱敏)
     * @param bankNumber 带银行卡的字符串
     * @return 脱敏后
     */
    public static String maskBankNumberAuto(String bankNumber) {
        if (bankNumber == null) {
            return null;
        }
        // bank number
        bankNumber = bankNumber.replaceAll("(^|[^\\d])[1-9]?\\d{11}(\\d{6})([^\\d]|$)", "$1************$2$3");
        bankNumber = bankNumber.replaceAll("(^|[^\\d])[1-9]?\\d{12}(\\d{6})([^\\d]|$)", "$1*************$2$3");
        bankNumber = bankNumber.replaceAll("(^|[^\\d])[1-9]?\\d{9}(\\d{6})([^\\d]|$)", "$1**********$2$3");
        return bankNumber;
    }

    /**
     * 脱敏邮箱
     * @param email 带邮箱的字符串
     * @return 脱敏后
     */
    public static String maskEmail(String email) {
        if (email == null) {
            return null;
        }
        // email
        email = email.replaceAll("[A-Za-z\\d][-_.A-Za-z\\d]*([-_.A-Za-z\\d]{2})(@(?:[A-Za-z\\d]+[-.])+[A-Za-z]{2,4})([^\\w]|$)", "****$1$2$3");
        return email;
    }

    /**
     * 脱敏邮箱
     * @param email 带邮箱的字符串
     * @return 脱敏后
     */
    public static String maskEmailAuto(String email) {
        if (email == null) {
            return null;
        }
        // email
        email = email.replaceAll("[A-Za-z\\d][-_.A-Za-z\\d]*([-_.A-Za-z\\d]{2})(@(?:[A-Za-z\\d]+[-.])+[A-Za-z]{2,4})([^\\w]|$)", "****$1$2$3");
        return email;
    }

    /**
     * 脱敏AKSK
     * @param akSK 带aksk的字符串
     * @return 脱敏后的字符串
     */
    public static String maskAKSK(String akSK) {
        if (akSK == null) {
            return null;
        }
        String origin = akSK;
        // AKID/AKKEY
        akSK = akSK.replaceAll("((?:^|[^\\w])\\w{4})(?:\\w{26}|\\w{12})([^\\w]|$)", "$1****$2");
        if (akSK.equals(origin)) {
            return keepEndpoint(akSK, 2, 2);
        }
        return akSK;
    }

    /**
     * 脱敏AKSK
     * @param akSK 带aksk的字符串
     * @return 脱敏后的字符串
     */
    public static String maskAKSKAuto(String akSK) {
        if (akSK == null) {
            return null;
        }
        // AKID/AKKEY
        akSK = akSK.replaceAll("((?:^|[^\\w])\\w{4})(?:\\w{26}|\\w{12})([^\\w]|$)", "$1****$2");
        return akSK;
    }

    /**
     * 自定义脱敏
     * @param message 待脱敏的字符串
     * @param matchPattern 脱敏匹配正则
     * @param replacePattern 脱敏替换规则
     * @return 脱敏后的串
     */
    public static String maskCustom(String message, String matchPattern, String replacePattern) {
        if (message == null) {
            return null;
        }
        return message.replaceAll(matchPattern, replacePattern);
    }

    /**
     * 保留后面部分字符
     * @param original 原始字符
     * @param suffixLength 保留多少个字符
     * @return 脱敏后
     */
    public static String keepBack(final String original, final int suffixLength) {
        if(original == null) {
            return null;
        }
        final int stringLength = original.length();
        if (stringLength <= suffixLength) {
            return original;
        }
        String suffix = original.substring(stringLength - suffixLength, stringLength);
        return "****" + suffix;
    }

    /**
     * 保留前面部分字符
     * @param original 原字符串
     * @param prefixLength 保留前几位
     * @return 隐藏后端字符串
     */
    public static String keepFront(final String original, final int prefixLength) {
        if(original == null) {
            return null;
        }
        final int stringLength = original.length();
        if (stringLength <= prefixLength) {
            return original;
        }
        String prefix = original.substring(0, prefixLength);
        return prefix + "****";
    }

    /**
     * 保留前后两端部分字符
     * @param original 原字符串
     * @return 隐藏后的字符串
     */
    public static String keepEndpoint(final String original) {
        if(original == null || original.length() <= 2) {
            return original;
        }
        final int stringLength = original.length();
        int shardLength = stringLength / 3;
        int frontLength = shardLength;
        if (stringLength % 3 > 0) {
            shardLength = shardLength + 1;
            frontLength = stringLength % shardLength;
        }
        StringBuilder res = new StringBuilder(original.substring(0, frontLength));
        for (int i = 0; i < shardLength; i++) {
            res.append("*");
        }
        res.append(original.substring(frontLength + shardLength));
        return res.toString();
    }

    /**
     * 保留前后两端部分字符
     * @param original 原字符串
     * @param keepFront 保留前x位
     * @param keepEnd 保留后x位
     * @return 隐藏后的字符串
     */
    public static String keepEndpoint(final String original, int keepFront, int keepEnd) {
        if(original == null || original.isEmpty()) {
            return original;
        }
        Assert.isTrue(keepFront >= 0 && keepEnd >= 0, "must >= 0");
        final int stringLength = original.length();
        final int keepLength =  keepFront + keepEnd;
        if (stringLength <= keepLength) {
            return original;
        }
        StringBuilder res = new StringBuilder(original.substring(0, keepFront));
        for (int i = 0; i < (stringLength - keepLength); i++) {
            res.append("*");
        }
        res.append(original.substring(stringLength - keepEnd));
        return res.toString();
    }

    /**
     * 整合参数
     * @param maskKey key
     * @param maskKeys 额外key
     * @return key集合
     */
    private static MaskKey[] integrateMaskKey(MaskKey maskKey, MaskKey... maskKeys) {
        if (maskKeys == null || maskKeys.length == 0) {
            return new MaskKey[]{maskKey};
        } else {
            int extraLength = maskKeys.length;
            MaskKey[] maskKeys1 = new MaskKey[extraLength + 1];
            System.arraycopy(maskKeys, 0, maskKeys1, 0, extraLength);
            maskKeys1[extraLength] = maskKey;
            return maskKeys1;
        }
    }

    /**
     * 处理json对象的脱敏 (根据 key 以及 maskKeys)
     * @param key 当前对象key
     * @param jsonObject 当前对象
     * @param maskKeys 需要脱敏的key
     * @return 脱敏结果
     */
    private static Object dealJsonMask(Object key, Object jsonObject, MaskKey[] maskKeys) {
        try {
            if (jsonObject instanceof JSONObject) {
                JSONObject jsonObj = (JSONObject)jsonObject;
                jsonObj.replaceAll((k, v) -> dealJsonMask(k, v, maskKeys));
                return jsonObj;
            }
            if (jsonObject instanceof JSONArray) {
                JSONArray arr = (JSONArray) jsonObject;
                for (int i = 0; i < arr.size(); i++) {
                    arr.set(i, dealJsonMask(key, arr.get(i), maskKeys));
                }
                return arr;
            }
            if (jsonObject instanceof String) {
                for (MaskKey maskKey : maskKeys) {
                    if (maskKey.getKeys().contains(key)) {
                        return mask((String)jsonObject, maskKey.getMaskType());
                    }
                }
            }
        } catch (Exception e) {
            LogContext.INTERNAL_LOGGER.warn("dealJsonMask failed, cause: {}", e.getMessage());
        }
        return jsonObject;
    }

}
